﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Web;

namespace Seven.Web.Http
{
    public class HttpUploadProcess
    {
        /// <summary>
        /// 临时文件后缀名
        /// </summary>
        private static string tempFilePostfix = ".td";

        /// <summary>
        /// 保存文件到指定路径，返回保存后的文件的长度
        /// </summary>
        /// <param name="fullSaveFilePath">完整的文件保存路径，含文件名</param>
        /// <param name="saveFilePath">文件保存路径，不含文件名。该参数用来防止“不存在保存路径就会保存”的情况，不存在路径则创建</param>
        /// <param name="file">要保存的文件信息</param>
        /// <param name="fullLength">输出参数，文件总长度</param>
        /// <returns></returns>
        public long SaveAs(string fullSaveFilePath, string saveFilePath, HttpPostedFileBase file, out long fullLength)
        {
            //开始读取位置
            long startPosition = 0;
            //结束读取位置
            long endPosition = 0;
            fullLength = 0;

            // 请求表头中的Content-Range用来记录本次请求中包含的文件的字节范围，此处规定Content-Range的值的格式为：
            // bytes a-b/c
            // 其中a表示起始位置，b表示结束位置，c表示文件总长度
            var contentRange = HttpContext.Current.Request.Headers["Content-Range"];
            this.TrySplitRange(contentRange, ref startPosition, ref endPosition, ref fullLength);

            // filename.ext.td 用来存放 目标文件数据的临时文件
            // filename.ext.td.cfg 用来存在 分片文件记录文件
            string temp = fullSaveFilePath + tempFilePostfix;
            string tempRecord = temp + ".txt";

            bool completeFileExist = System.IO.File.Exists(fullSaveFilePath);//完整的文件是否存在
            try
            {
                if (completeFileExist)
                {
                    #region 若完整文件已经存在，则直接返回文件长度
                    using (System.IO.FileStream fs = System.IO.File.OpenWrite(fullSaveFilePath))
                    {
                        return fs.Length;
                    }
                    #endregion
                }
                else
                {
                    StreamWriter testsw = new StreamWriter(temp + ".test.txt", true);
                    testsw.WriteLine(fullSaveFilePath + " 开始");
                    testsw.WriteLine("此分片信息是：" + contentRange);
                    //若完整文件不存在，则找分片记录文件
                    bool tempFileExist = System.IO.File.Exists(temp);//临时文件是否存在
                    bool recordFileExist = System.IO.File.Exists(tempRecord);//记录文件是否存在

                    //要记录的分片信息
                    string recordInfo = contentRange;
                    byte[] rbytes = Serialize(recordInfo);
                    //表示往临时文件中写入当前分片数据是否成功。只有成功的情况下，才往记录文件中写入分片信息
                    bool success = false;
                    testsw.WriteLine("初次检测到 临时文件 " + (tempFileExist ? "存在" : "不存在"));
                    if (recordFileExist)
                    {
                        testsw.WriteLine("检测到 记录文件 存在，开始检测 记录信息 是否存在");
                        #region 若记录文件存在

                        #region 先根据记录信息，判定当前分片信息是否存在
                        bool recordExist = false;
                        using (System.IO.FileStream fs = new System.IO.FileStream(tempRecord, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite))
                        {
                            using (StreamReader sr = new StreamReader(fs))
                            {
                                string line = sr.ReadLine();
                                while (line != null)
                                {
                                    if (string.IsNullOrWhiteSpace(line)) { line = sr.ReadLine(); continue; }
                                    if (line == recordInfo)
                                    { recordExist = true; break; }
                                    else { line = sr.ReadLine(); }
                                }
                            }
                        }
                        #endregion
                        testsw.WriteLine("检测到 记录信息 " + (recordExist ? "存在" : "不存在"));
                        #region 若记录信息不存在
                        if (!recordExist)
                        {
                            if (!tempFileExist) { tempFileExist = System.IO.File.Exists(temp); }
                            testsw.WriteLine("再次检测到 临时文件 " + (tempFileExist ? "存在" : "不存在"));
                            if (!tempFileExist)
                            {
                                testsw.WriteLine("记录文件存在，且分片信息不存在，但临时文件不存在。这种情况下，临时文件应该被创建并写入数据。");
                                #region  记录文件存在，且分片信息不存在，但临时文件不存在。这种情况下，临时文件应该被创建并写入数据。
                                try
                                {
                                    //若临时文件不存在，则创建该临时文件，并写入当前分片文件的数据。
                                    using (System.IO.FileStream fs = new System.IO.FileStream(temp, System.IO.FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                                    {
                                        fs.Lock(startPosition, endPosition - startPosition + 1);
                                        fs.Seek(startPosition, System.IO.SeekOrigin.Current);

                                        byte[] nbytes = new byte[1024];
                                        int nReadSize = 0;
                                        nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
                                        while (nReadSize > 0)
                                        {
                                            fs.Write(nbytes, 0, nReadSize);
                                            nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
                                        }

                                        fs.Unlock(startPosition, endPosition - startPosition + 1);
                                        success = true;
                                    }
                                }
                                catch (Exception e1) { }
                                #endregion
                            }
                            else
                            {
                                testsw.WriteLine("若临时文件存在，则写入当前分片数据");
                                #region 若临时文件存在，则写入当前分片数据
                                try
                                {
                                    //using (System.IO.FileStream fs = System.IO.File.OpenWrite(temp))
                                    using (System.IO.FileStream fs = new System.IO.FileStream(temp, System.IO.FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                                    {
                                        fs.Lock(startPosition, endPosition - startPosition + 1);
                                        fs.Seek(startPosition, System.IO.SeekOrigin.Current);

                                        byte[] nbytes = new byte[1024];
                                        int nReadSize = 0;
                                        nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
                                        while (nReadSize > 0)
                                        {
                                            fs.Write(nbytes, 0, nReadSize);
                                            nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
                                        }

                                        fs.Unlock(startPosition, endPosition - startPosition + 1);
                                        success = true;
                                    }
                                }
                                catch (Exception e1) { }
                                #endregion
                            }
                        }
                        #endregion

                        #endregion
                    }
                    else
                    {
                        testsw.WriteLine("检测到记录文件不存在");
                        #region 若记录文件不存在
                        //记录文件不存在，则

                        if (tempFileExist)
                        {
                            #region  记录文件不存在，但临时文件存在。这种情况下，临时文件应该新建覆盖并写入数据。
                            try
                            {
                                using (System.IO.FileStream fs = new System.IO.FileStream(temp, System.IO.FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                                {
                                    fs.Lock(startPosition, endPosition - startPosition + 1);
                                    fs.Seek(startPosition, System.IO.SeekOrigin.Current);

                                    byte[] nbytes = new byte[1024];
                                    int nReadSize = 0;
                                    nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
                                    while (nReadSize > 0)
                                    {
                                        fs.Write(nbytes, 0, nReadSize);
                                        nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
                                    }

                                    fs.Unlock(startPosition, endPosition - startPosition + 1);
                                    success = true;
                                }
                            }
                            catch (Exception e1) { }
                            #endregion
                        }
                        else
                        {
                            #region 若临时文件不存在
                            try
                            {
                                //若临时文件不存在，则创建该临时文件，并写入当前分片文件的数据。
                                using (System.IO.FileStream fs = new System.IO.FileStream(temp, System.IO.FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                                {
                                    fs.Lock(startPosition, endPosition - startPosition + 1);
                                    fs.Seek(startPosition, System.IO.SeekOrigin.Current);

                                    byte[] nbytes = new byte[1024];
                                    int nReadSize = 0;
                                    nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
                                    while (nReadSize > 0)
                                    {
                                        fs.Write(nbytes, 0, nReadSize);
                                        nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
                                    }

                                    fs.Unlock(startPosition, endPosition - startPosition + 1);
                                    success = true;
                                }
                            }
                            catch (Exception e1) { }
                            #endregion
                        }

                        #endregion
                    }

                    if (success)
                    {
                        //防止另一个线程已经创建记录文件，这里再次判定
                        if (!recordFileExist) { recordFileExist = System.IO.File.Exists(tempRecord); }
                        #region 数据写入成功后，创建该记录文件，并写入当前分片文件信息

                        FileMode fm = recordFileExist ? FileMode.Append : FileMode.Create;
                        FileAccess fa = FileAccess.Write;
                        using (System.IO.FileStream fs = new System.IO.FileStream(tempRecord, fm, fa, System.IO.FileShare.ReadWrite))
                        {
                            using (StreamWriter sw = new StreamWriter(fs))
                            {
                                sw.WriteLine(recordInfo);
                            }
                        }

                        #endregion
                    }
                    testsw.WriteLine(fullSaveFilePath + " 结束");
                    testsw.Close();
                }
            }
            catch (Exception e1) { }


            //try
            //{

            //    if (!completeFileExist)
            //    {
            //        // 文件不存在，第一次写入，localLength为0
            //        using (System.IO.FileStream fs = new System.IO.FileStream(fullSaveFilePath, System.IO.FileMode.Create))
            //        {
            //            fs.Lock(startPosition, endPosition - startPosition + 1);
            //            fs.Seek(startPosition, System.IO.SeekOrigin.Current);

            //            byte[] nbytes = new byte[1024];
            //            int nReadSize = 0;
            //            nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
            //            while (nReadSize > 0)
            //            {
            //                fs.Write(nbytes, 0, nReadSize);
            //                nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
            //            }

            //            fs.Unlock(startPosition, endPosition - startPosition + 1);
            //        }
            //    }
            //    else
            //    {
            //        //文件已存在
            //        using (System.IO.FileStream fs = System.IO.File.OpenWrite(fullSaveFilePath))
            //        {
            //            localLength = fs.Length;
            //            fs.Lock(startPosition, endPosition - startPosition + 1);
            //            fs.Seek(startPosition, System.IO.SeekOrigin.Current);

            //            byte[] nbytes = new byte[1024];
            //            int nReadSize = 0;
            //            nReadSize = file.InputStream.Read(nbytes, 0, nbytes.Length);
            //            while (nReadSize > 0)
            //            {
            //                fs.Write(nbytes, 0, nReadSize);
            //                nReadSize = file.InputStream.Read(nbytes, 0, nReadSize);
            //            }

            //            fs.Unlock(startPosition, endPosition - startPosition + 1);
            //        }
            //    }
            //}
            //catch (Exception ex)
            //{
            //    //throw new Exception(ex.Message);
            //}

            return endPosition;
        }

        public bool DealTemp(string filePath, string md5)
        {
            bool success = false;
            string temp = filePath + tempFilePostfix;
            bool tempFileExist = System.IO.File.Exists(temp);

            if (tempFileExist)
            {
                try
                {
                    FileInfo file = new FileInfo(temp);
                    file.CopyTo(filePath, true);
                    System.IO.File.Delete(temp);
                    success = true;
                }
                catch (Exception e)
                { }
            }

            return success;
        }

        /// <summary>
        /// 将content-range分割，获取起始位置、结束位置和总长度
        /// 若分割失败，则失败部分的引用参数的值为0
        /// </summary>
        /// <param name="range">要分割的range字符串</param>
        /// <param name="startPosition">引用参数，起始位置</param>
        /// <param name="endPosition">引用参数，结束位置</param>
        /// <param name="fullLength">引用参数，总长度</param>
        private void TrySplitRange(string range, ref long startPosition, ref long endPosition, ref long fullLength)
        {
            if (!string.IsNullOrWhiteSpace(range))
            {
                range = range.ToLower();
                if (range.IndexOf("bytes") > -1)
                {
                    range = range.Replace("bytes", "").Trim();
                    int temp = range.IndexOf("/");
                    if (temp > -1)
                    {
                        string fullLen = range.Substring(temp + 1);
                        long.TryParse(fullLen, out fullLength);

                        range = range.Substring(0, temp);
                        string[] ranges = range.Split(new char[] { '-' });
                        if (ranges.Length > 1)
                        {
                            long.TryParse(ranges[0], out startPosition);
                            long.TryParse(ranges[1], out endPosition);
                        }
                    }
                }
            }
        }

        private static byte[] Serialize<T>(T o)
        {
            if (o == null)
                return null;

            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, o);
                byte[] data = stream.ToArray();
                return data;
            }
        }

        private static T Deserialize<T>(byte[] data)
        {
            if (data == null || data.Length == 0)
                return default(T);

            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream(data))
            {
                T result = (T)formatter.Deserialize(stream);
                return result;
            }
        }
    }
}
